﻿using System;
using System.Collections.Generic;

namespace Super
{
	public static class EventManager
	{
		/// <summary>		优先级2			
		///								
		///								对象1-委托1
		///			事件->	优先级1 ->	对象2-委托2    
		///								对象3-委托3
		///						
		/// </summary>		优先级0
		private static Dictionary<Type, Dictionary<int, Dictionary<object, Action<IGameEvent>>>> eventDatas =
			new Dictionary<Type, Dictionary<int, Dictionary<object, Action<IGameEvent>>>>();

		/// <summary>
		/// 方便事件移除时，数据查找
		/// </summary>
		private static Dictionary<object, HashSet<GameEventNode>> gameDatas = new Dictionary<object, HashSet<GameEventNode>>();

		private static Dictionary<Type, List<int>> eventOrderDatas = new Dictionary<Type, List<int>>();

		/// <summary>
		/// 因为这个action是匿名委托，所以需要target确定object对象
		/// </summary>
		/// <param name="GE_Type"></param>
		/// <param name="action"></param>
		/// <param name="target"></param>
		/// <param name="priority"></param>
		private static void Register(Type GE_Type, Action<IGameEvent> action, object target, int priority = 0)
		{
			Dictionary<int, Dictionary<object, Action<IGameEvent>>> priorityDatas = GetPriorityDatas(GE_Type);
			Dictionary<object, Action<IGameEvent>> gameEventDatas = GetGameEventData(priorityDatas, priority);
			AddGameActionData(gameEventDatas, action, target);
			AddGameData(GE_Type, action, priority, target);
			AddGameOrderData(GE_Type, priority);
		}

		/// <summary>
		/// 添加事件监听（!!!匿名委托无法取消!!!)
		/// </summary>
		/// <typeparam name="T">事件类型对应的事件数据类</typeparam>
		/// <param name="action">事件委托回调</param>
		/// <param name="priority">优先级</param>
		public static void Register<T>(Action<T> action, int priority = 0) where T : IGameEvent
		{
			Type type = typeof(T);
			object target = action.Target;
			Register(type, (data) => action((T)data), target, priority);
		}

		/// <summary>
		/// 添加事件监听
		/// 因为匿名委托的target是函数的环境，所以要传入类中的this，以便后续事件的移除
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="target">实例化类</param>
		/// <param name="action">事件委托回调</param>
		/// <param name="priority">优先级</param>
		public static void Register<T>(object target, Action<T> action, int priority = 0) where T : IGameEvent
		{
			System.Type type = typeof(T);
			Type GE = (Type)type.GetProperty("gameEvent").GetValue(null);
			Register(GE, (data) => action((T)data), target, priority);
		}

		/// <summary>
		/// 移除事件监听
		/// </summary>
		/// <param name="target">移除事件的对象</param>
		/// <param name="GE_Type">移除相应的事件类型</param>
		/// <param name="priority">事件的优先级，注意优先级对应</param>
		public static void UnRegister(object target, Type GE_Type, int priority = 0)
		{
			Dictionary<int, Dictionary<object, Action<IGameEvent>>> priorityDatas = GetPriorityDatas(GE_Type);
			Dictionary<object, Action<IGameEvent>> gameEventDatas = GetGameEventData(priorityDatas, priority);
			gameEventDatas.Remove(target);
			RemoveGameData(target, GE_Type, priority);
			TryRemoveGameOrderData(GE_Type);
		}

		/// <summary>
		/// 移除事件监听
		/// </summary>
		/// <param name="target">移除事件的对象</param>
		/// <param name="GE_Type">移除相应的事件类型</param>
		/// <param name="priority">事件的优先级，注意优先级对应</param>
		public static void UnRegister<T>(object target, int priority = 0)
		{
			Type GE_Type = typeof(T);
			Dictionary<int, Dictionary<object, Action<IGameEvent>>> priorityDatas = GetPriorityDatas(GE_Type);
			Dictionary<object, Action<IGameEvent>> gameEventDatas = GetGameEventData(priorityDatas, priority);
			gameEventDatas.Remove(target);
			RemoveGameData(target, GE_Type, priority);
			TryRemoveGameOrderData(GE_Type);
		}

		/// <summary>
		/// 移除对象身上所有监听
		/// </summary>
		/// <param name="target"></param>
		public static void UnRegisterAll(object target)
		{
			if (!gameDatas.ContainsKey(target))
				return;

			HashSet<GameEventNode> nodes = new HashSet<GameEventNode>(gameDatas[target]);
			foreach (var item in nodes)
			{
				UnRegister(target, item.GE_Type, item.priority);
			}
		}

		/// <summary>
		/// 触发事件
		/// </summary>
		/// <param name="EventData">事件所需要的参数，继承IGameEvent接口的类</param>
		public static void Emit(IGameEvent eventData)
		{
			Type GE_Type = eventData.GetType();
			if (!eventDatas.ContainsKey(GE_Type))
				return;

			Dictionary<int, Dictionary<object, Action<IGameEvent>>> priorityDatas = GetPriorityDatas(GE_Type);
			Dictionary<object, Action<IGameEvent>> gameEventDatas;
			if (eventOrderDatas.TryGetValue(GE_Type, out List<int> orderList))
			{
				foreach (var priority in new List<int>(orderList))
				{
					gameEventDatas = new Dictionary<object, Action<IGameEvent>>(GetGameEventData(priorityDatas, priority));
					foreach (var action in gameEventDatas.Values)
					{
						action(eventData);
					}
				}
			}
		}

		private static Dictionary<int, Dictionary<object, Action<IGameEvent>>> GetPriorityDatas(Type GE_Type)
		{
			if (!eventDatas.ContainsKey(GE_Type))
			{
				eventDatas.Add(GE_Type, new Dictionary<int, Dictionary<object, Action<IGameEvent>>>());
			}
			return eventDatas[GE_Type];
		}

		private static Dictionary<object, Action<IGameEvent>> GetGameEventData(Dictionary<int, Dictionary<object, Action<IGameEvent>>> priorityDatas, int priority)
		{
			if (!priorityDatas.ContainsKey(priority))
			{
				priorityDatas.Add(priority, new Dictionary<object, Action<IGameEvent>>());
			}
			return priorityDatas[priority];
		}

		private static void AddGameActionData(Dictionary<object, Action<IGameEvent>> gameEventData, Action<IGameEvent> action, object target)
		{
			if (!gameEventData.ContainsKey(target))
			{
				gameEventData.Add(target, action);
			}
			else
			{
				gameEventData[target] = action;
			}
		}

		private static void AddGameData(Type GE_Type, Action<IGameEvent> action, int priority, object target)
		{
			if (!gameDatas.ContainsKey(target))
			{
				gameDatas.Add(target, new HashSet<GameEventNode>());
			}

			HashSet<GameEventNode> nodes = gameDatas[target];
			GameEventNode node = new GameEventNode();
			node.GE_Type = GE_Type;
			node.priority = priority;
			nodes.Add(node);
		}

		private static void AddGameOrderData(Type GE_Type, int priority)
		{
			if (!eventOrderDatas.ContainsKey(GE_Type))
			{
				eventOrderDatas.Add(GE_Type, new List<int>());
			}

			List<int> order = eventOrderDatas[GE_Type];
			if (!order.Contains(priority))
			{
				order.Add(priority);
				order.Sort((x, y) => -x.CompareTo(y));
			}
		}
		private static void RemoveGameData(object target, Type GE_Type, int priority = 0)
		{
			if (gameDatas.ContainsKey(target))
			{
				gameDatas[target].RemoveWhere(item =>
				{
					return item.GE_Type == GE_Type && item.priority == priority;
				});
			}
		}

		private static void TryRemoveGameOrderData(Type GE_Type)
		{
			bool eventDataOk = eventDatas.ContainsKey(GE_Type) && eventDatas[GE_Type].Count > 0;
			bool eventDataPriOk = false;
			if (eventOrderDatas.ContainsKey(GE_Type))
			{
				List<int> order = new List<int>(eventOrderDatas[GE_Type]);
				foreach (var item in order)
				{
					eventDataPriOk = eventDataOk && eventDatas[GE_Type].ContainsKey(item) && eventDatas[GE_Type][item].Count > 0;
					if (!(eventDataPriOk && eventDataPriOk))
						eventOrderDatas[GE_Type].Remove(item);
				}
				eventOrderDatas[GE_Type].Sort((x, y) => -x.CompareTo(y));
			}
		}
	}
}
